package com.tianji.learning.service.impl;

import cn.hutool.core.util.ObjectUtil;
import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.leanring.LearningLessonDTO;
import com.tianji.api.dto.leanring.LearningRecordDTO;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.AssertUtils;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.domain.dto.LearningRecordFormDTO;
import com.tianji.learning.domain.po.LearningLesson;
import com.tianji.learning.domain.po.LearningRecord;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.tianji.learning.service.ILearningRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.learning.utils.LearningRecordDelayTaskHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

import static com.tianji.common.constants.MqConstants.Exchange.LEARNING_EXCHANGE;
import static com.tianji.common.constants.MqConstants.Key.LEARN_SECTION;
import static com.tianji.learning.enums.LessonStatus.NOT_BEGIN;
import static com.tianji.learning.enums.SectionType.VIDEO;

/**
 * <p>
 * 学习记录表 服务实现类
 * </p>
 *
 * @author wangyuqing
 * @since 2024-08-22
 */
@Service
@Transactional
@RequiredArgsConstructor
public class LearningRecordServiceImpl extends ServiceImpl<LearningRecordMapper, LearningRecord> implements ILearningRecordService {
    private final ILearningLessonService lessonService;
    private final CourseClient courseClient;
    private final LearningRecordDelayTaskHandler delayTaskHandler;
    private final RabbitMqHelper mqHelper;

    /**
     * 1.查询学习记录
     *
     * @param courseId
     * @return
     */
    @Override
    public LearningLessonDTO queryLearningRecordByCourse(Long courseId) {
        //获取当前用户ID
        Long userId = UserContext.getUser();
        //查询课表信息
        LearningLesson lesson = lessonService.queryByUserAndCourseId(userId, courseId);
        // 查询学习记录
        // select * from xx where lesson_id = #{lessonId}
        List<LearningRecord> records = lambdaQuery()
                .eq(LearningRecord::getLessonId, lesson.getId()).list();
        // 4.封装结果
        LearningLessonDTO dto = new LearningLessonDTO();
        dto.setId(lesson.getId());
        dto.setLatestSectionId(lesson.getLatestSectionId());
        dto.setRecords(BeanUtils.copyList(records, LearningRecordDTO.class));
        return dto;
    }

    /**
     * 2.提交学习记录
     *
     * @param formDTO 前端提交学习记录数据
     * @return
     */
    @Override
    public void addLearningRecord(LearningRecordFormDTO formDTO) {
        //1.处理视频学习数据
        //1.1 判断小节状态来执行不同的方法
        Boolean firstFinished = formDTO.getSectionType() == VIDEO ? handleVideoRecord(formDTO) : handleExamRecord(formDTO);
        //3.处理课表数据
        handleLessonRecord(formDTO, firstFinished);
    }


    /**
     * 处理考试学习数据方法
     *
     * @param formDTO
     * @return
     */
    private Boolean handleExamRecord(LearningRecordFormDTO formDTO) {
        //1.将DTO转换为PO
        LearningRecord record = BeanUtils.copyBean(formDTO, LearningRecord.class);
        //2.填充缺失字段
        record.setUserId(UserContext.getUser());
        record.setFinished(true);
        record.setFinishTime(formDTO.getCommitTime());
        //3.执行保存
        boolean save = save(record);
        if (!save) {
            throw new DbException("保存学习记录失败");
        }
        return true;
    }

    /**
     * 处理视频数据方法
     *
     * @param formDTO
     * @return
     */
    private Boolean handleVideoRecord(LearningRecordFormDTO formDTO) {
        //1.查询旧的学习记录
        LearningRecord oldRecord = queryOldRecord(formDTO.getLessonId(), formDTO.getSectionId());
        //2.判断学习记录是否存在
        if (ObjectUtil.isEmpty(oldRecord)) {
            //3.如果不存在，新增学习记录
            //3.1.将DTO转换为PO
            LearningRecord record = BeanUtils.copyBean(formDTO, LearningRecord.class);
            //3.2.填充缺失字段
            record.setUserId(UserContext.getUser());
            //3.3执行保存
            boolean save = save(record);
            if (!save) {
                throw new DbException("新增学习记录失败");
            }
            //3.5返回标识
            return false;
        }

        //4.如果存在，修改学习记录
        //4.1判断是否时候第一次学习完当前小节
        boolean firstFinished = !oldRecord.getFinished() && formDTO.getMoment() << 1 >= formDTO.getDuration();
        //如果不是第一次学完，将学习记录暂存到缓存中，并且提交一个延迟任务
        if (!firstFinished) {
            LearningRecord learningRecord = new LearningRecord();
            learningRecord.setLessonId(formDTO.getLessonId());
            learningRecord.setSectionId(formDTO.getSectionId());
            learningRecord.setMoment(formDTO.getMoment());
            learningRecord.setFinished(oldRecord.getFinished());
            learningRecord.setId(oldRecord.getId());
            delayTaskHandler.addLearningRecordDelayCheckTask(learningRecord);
            formDTO.setDuration(-1);
            return false;

        }
        //4.2更新学习记录，不管是否是第一次学习完当前小节，必须更新的字段moment，如果是第一次，需要多更新finished，finish_time两个字段
        boolean updateResult = lambdaUpdate().set(LearningRecord::getMoment, formDTO.getMoment())
                .set(firstFinished, LearningRecord::getFinished, true)
                .set(firstFinished, LearningRecord::getFinishTime, formDTO.getCommitTime())
                .eq(LearningRecord::getId, oldRecord.getId())
                .update();
        if (!updateResult) {
            throw new DbException("更新学习记录失败");
        }
        //4.3发送消息添加积分
        try {
            mqHelper.send(LEARNING_EXCHANGE, LEARN_SECTION, UserContext.getUser());
        } catch (Exception e) {
            log.error("发送学习视频积分记录失败", e);
        }
        //清理redis缓存，保持双写一致性
        delayTaskHandler.deleteLearningRecordDelayCheckTask(formDTO.getLessonId(), formDTO.getSectionId());
        return true;
    }

    /**
     * 查询旧的学习记录
     *
     * @param lessonId 课程Id
     * @Param sectionId 小节Id
     */
    private LearningRecord queryOldRecord(Long lessonId, Long sectionId) {
        //1.从缓存询旧的学习记录
        LearningRecord oldRecord = delayTaskHandler.readLearningRecordToCache(lessonId, sectionId);
        //2.判断是否存在
        if (ObjectUtil.isNotEmpty(oldRecord)) {
            //3.如果存在，直接返回
            return oldRecord;
        }
        //4.如果不存在，查询数据库
        oldRecord = this.lambdaQuery().eq(LearningRecord::getLessonId, lessonId)
                .eq(LearningRecord::getSectionId, sectionId).one();
        //如果数据库不存在，返回null，写入缓存
        if (ObjectUtil.isEmpty(oldRecord)) {
            return null;
        }
        delayTaskHandler.writeLearningRecordToCache(oldRecord);
        return oldRecord;
    }

    /**
     * 处理课表数据方法
     *
     * @param formDTO
     * @return
     */
    private void handleLessonRecord(LearningRecordFormDTO formDTO, Boolean firstFinished) {
        //1.判断是否有新的小节学完、
        boolean allLearned = false;
        //2.如果有，查询课表信息(已学习小节数，课表ID)
        LearningLesson lesson = this.lessonService.getById(formDTO.getLessonId());
        AssertUtils.isNotNull(lesson, "课表信息不存在，无法更新数据");

        if (firstFinished) {
            //3.1如果有，查询课程信息
            CourseFullInfoDTO courseInfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
            AssertUtils.isNotNull(courseInfo, "课程信息不存在，无法更新数据");
            //3.2、比较课程是否已经全部学完（已学完小节数 + 1 >= 课程总小节数）
            allLearned = lesson.getLearnedSections() + 1 >= courseInfo.getSectionNum();
        }
        //4.根据是否全部学完，更新课表信息
        boolean updateResult = lessonService.lambdaUpdate().set(LearningLesson::getLatestSectionId, formDTO.getSectionId())//最近学习小节ID
                .set(LearningLesson::getLatestLearnTime, formDTO.getCommitTime())//最近学习时间
                .set(firstFinished, LearningLesson::getLearnedSections, lesson.getLearnedSections() + 1)//// 如果该小节已学完，将已学习小节数 +1
                .set(lesson.getStatus() == NOT_BEGIN, LearningLesson::getStatus, LessonStatus.LEARNING)//如果第一次学习，将课表状态改为学习中
                .set(allLearned, LearningLesson::getStatus, LessonStatus.FINISHED)//如果全部学完，将课表状态改为已学完
                .eq(LearningLesson::getId, lesson.getId())
                .update();
        if (!updateResult) {
            throw new DbException("修改课表数据失败！");
        }
    }
}
